package com.yb.custom.annotation.log.annotation.handler;

import com.alibaba.fastjson.JSONObject;
import com.yb.custom.annotation.log.annotation.LogRecord;
import com.yb.custom.annotation.log.entity.OperateLog;
import com.yb.custom.annotation.log.exception.CustomException;
import com.yb.custom.annotation.log.util.LoginUserUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 注解@within(com.cxh.study.aop.controller.UserAccessAnnotation)
 * 表示拦截含有com.cxh.study.aop.controller.UserAccessAnnotation这个注解的类中所有方法
 * 注解@annotation(com.cxh.study.aop.controller.UserAccessAnnotation)
 * 表示拦截含有com.cxh.study.aop.controller.UserAccessAnnotation这个注解的方法
 *
 * @author yangbiao
 * @date 2022/3/30
 */
@Slf4j
@Aspect
@Component
public class LogRecordHandler {

    /**
     * 切入点签名,切入点表达式,可以指定包范围,可以指定注解,还可以两个一起用
     */
    @Pointcut(value = "@annotation(com.yb.custom.annotation.log.annotation.LogRecord)")
    public void logRecordPointcut() {
    }

    /**
     * 这里只用了环绕切的方式,如有需要,可另行添加其他的方式
     * 这里添加事务注解是不能回滚的,也不需要回滚
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "logRecordPointcut()")
    public Object logRecordAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取日志对象实例
        OperateLog operateLog = new OperateLog();
        try {
            //获取签名
            Signature signature = joinPoint.getSignature();
            //判断签名是否属性指定的签名类型--->这里我只处理方法的签名(注解也只允许在方法上注解)
            if (signature instanceof MethodSignature) {
                //获取方法签名
                Method method = ((MethodSignature) signature).getMethod();
                //获取方法上的自定义注解
                LogRecord annotation = method.getAnnotation(LogRecord.class);
                //校验直接在这里来做,不要使用自定义校验处理器来做,那个是处理属性校验的
                final int logTpe = annotation.logTpe();
                //获取登录类型值并判断是否是合法的
                final boolean logTypeOne = logTpe != 1;
                final boolean logTypeTwo = logTpe != 2;
                if (logTypeOne && logTypeTwo) {
                    CustomException.message("未知的日志类型");
                }
                //获取操作内容并判断是否是合法的(必填)
                String operateContent = annotation.operateContent();
                final boolean tooLength = operateContent.length() > 20;
                if (tooLength) {
                    CustomException.message("操作内容不能超过20字");
                }
                //通过用户工具获取登录用户的相关信息(有过滤器处理用户登录设置)
                final LoginUserUtils.LoginUser loginUser = LoginUserUtils.getLoginUser();
                //设置日志信息
                operateLog.setLogType(logTpe);
                operateLog.setOperateContent(operateContent);
                operateLog.setOperateUsername(loginUser.getUsername());
                operateLog.setRequestType(loginUser.getRequestType());
                operateLog.setOperateIp(loginUser.getRequestIp());
                operateLog.setUserFullName(loginUser.getFullName());
                operateLog.setOperateTime(LocalDateTime.now());
                //拷贝对应属性值到日志实体对象
                BeanUtils.copyProperties(loginUser, operateLog);
                //1.直接保存到数据库,其实这里应该是互斥的,有一种方式处理日志数据即可
                operateLog.insert();
                //2.推送到kafka等 中间件(有其他服务处理)

                //输出日志
            }
        } catch (Throwable e) {
            log.error("记录操作日志内容: {} \n异常: {}", JSONObject.toJSONString(operateLog), e.getMessage(), e);
            if (e instanceof CustomException) {
                CustomException.message(e.getMessage());
            }
            CustomException.message("网络异常...");
        }
        //继续执行下一个通知或目标方法调用,参数是Object[]类型的
        return joinPoint.proceed();
    }

}
